###### STD for Reset language
# This file is designed monolithic to be easy to copy and include in any
# project. Contains everything to create anything.
######

### Constants
## Boolean values
FALSE => 0;
TRUE => 1;
## Null value
NULL => 0;
## EOF value
EOF => -1;

### Useful functions
## panic - show panic message and quit
# a0 - message
panic : (a0) {
  # Printing message
  syscall(4, 1, "PANIC: ", 7);
  a0 = a0 ? a0 : "Some function generated panic";
  syscall(4, 1, a0, strlen(a0));
  syscall(4, 1, "\n", 1);
  # Exitting
  exit(1);
}

## assert - make an assertion
# a0 - condition
# a1 - message
assert : (a0, a1) {
  # Checking for condition to be NULL
  if (!a0) {
    # Printing message
    syscall(4, 1, "ASSERT: ", 8);
    a1 = a1 ? a1 : "Some function made assertion";
    # a1 = "Some function made assertion";
    syscall(4, 1, a1, strlen(a1));
    syscall(4, 1, "\n", 1);
    # Exitting
    exit(1);
  }
}

## max - find maximum integer
# a0 - first integer
# a1 - second integer
# @return maximum integer
max : (a0, a1) { return a0 < a1 ? a1 : a0; }

## min - find minimum integer
# a0 - first integer
# a1 - second integer
# @return minimum integer
min : (a0, a1) { return a0 > a1 ? a1 : a0; }

### System calls
## SYS_exit - exit with code
# a0 - exit code
SYS_exit => 1;
exit : (a0) { syscall(SYS_exit, a0); }

## SYS_read - read from file
# a0 - file
# a1 - buffer
# a2 - count
# @return read count
SYS_read => 3;
read : (a0, a1, a2) { return syscall(SYS_read, a0, a1, a2); }

## SYS_write - write to file
# a0 - file
# a1 - buffer
# a2 - count
# @return write count
SYS_write => 4;
write : (a0, a1, a2) { return syscall(SYS_write, a0, a1, a2); }

## SYS_open - open file
# a0 - path
# a1 - flags
# a2 - mode
# @return fd or error
SYS_open => 5;
open : (a0, a1, a2) { return syscall(SYS_open, a0, a1, a2); }

## SYS_close - close file
# a0 - fd
# @return result code
SYS_close => 6;
close : (a0) { return syscall(SYS_close, a0); }

## SYS_brk - set heap segment limit
# a0 - address or NULL
# @return current heap limit
SYS_brk => 45;
brk : (a0) { return syscall(SYS_brk, a0); }

#### Memory Manager Module
### Constants
## Align size
MMM_TLSF_ALIGN_SIZE_LOG2     => 2;
MMM_TLSF_ALIGN_SIZE          => 1 << MMM_TLSF_ALIGN_SIZE_LOG2;
## Second level index
MMM_TLSF_SL_INDEX_COUNT_LOG2 => 5;
MMM_TLSF_SL_INDEX_COUNT      => 1 << MMM_TLSF_SL_INDEX_COUNT_LOG2;
## First level index
# Maximum first level index
MMM_TLSF_FL_INDEX_MAX        => 30;
# First level shift
MMM_TLSF_FL_INDEX_SHIFT      => MMM_TLSF_SL_INDEX_COUNT_LOG2 + MMM_TLSF_ALIGN_SIZE_LOG2;
MMM_TLSF_FL_INDEX_COUNT      => MMM_TLSF_FL_INDEX_MAX - MMM_TLSF_FL_INDEX_SHIFT + 1;
## Small block size
MMM_TLSF_SMALL_BLOCK         => 1 << MMM_TLSF_FL_INDEX_SHIFT;
### Variables
# Current sbrk heap limit
sbrk_current : NULL;
default_memory_pool : NULL;

### Helper functions
## sbrk - increment heap segment limit
# a0 - size
# @return previous heap limit
sbrk : (a0) {
  if (!sbrk_current) sbrk_current = brk(NULL);
  if (a0 <= 0) return sbrk_current;
  sbrk_current += a0;
  brk(sbrk_current);
  return sbrk_current - a0;
}

## ffs - find first bit set in number
# a0 - number
# @return bit index numbered from 0
ffs : (a0) {
  asm {
    movl 8(%ebp), %eax;
    bsrl %eax, %eax;
  }
}

## fls - find last bit set in number
# a0 - number
# @return bit index numbered from 0
fls : (a0) {
  asm {
    movl 8(%ebp), %eax;
    bsfl %eax, %eax;
  }
}

## align_up - align number up to another number (which is power of 2)
# a0 - number to be aligned
# a1 - number align to
# @return aligned number
align_up : (a0, a1) {
  return (a0 + a1 - 1) & ~(a1 - 1);
}

## align_up - align number down to another number (which is power of 2)
# a0 - number to be aligned
# a1 - number align to
# @return aligned number
align_down : (a0, a1) {
  return a0 - (a0 & (a1 - 1));
}

### Memory/String operations
## Fill memory block with value (char)
# a0 - value
# a1 - address
# a2 - size
memset : (a0, a1, a2) {
  # x0 - pos
  allocate(1);
  x0 = 0;
  # Writing value to memory block
  while (x0 < a2) {
    # Writing byte
    writechar(a1, x0, a0);
    # Moving to next byte
    x0 = x0 + 1;
  }
}

## Copy data from one memory block to another
# a0 - src
# a1 - dest
# a2 - size
memcpy : (a0, a1, a2) {
  # x0 - pos
  allocate(1);
  x0 = 0;
  while (x0 < a2) {
    # Writing byte
    writechar(a1, x0, readchar(a0, x0));
    # Moving to next byte
    x0 = x0 + 1;
  }
}

## Get length of string
# a0 - string
# @return string length
strlen : (a0) {
  # x0 - string length
  allocate(1);
  x0 = 0;
  # While char is not NULL, incrementing length
  while (readchar(a0, x0) != '\0') { x0 = x0 + 1; }
  return x0;
}

## Unsigned int to string
# a0 - value
# a1 - result buffer
# a2 - base
utoa_digits : "0123456789abcdefghijklmnopqrstuvwxyz";
utoa : (a0, a1, a2) {
  # x0 - i
  # x1 - j
  # x2 - tmp
  allocate(3);
  x0 = 0;
  # Converting number to string of digits
  do {
    # Writing needed char to result buffer and moving to next char
    writechar(a1, x0++, readchar(utoa_digits, a0 % a2));
    # Going to next unit
    a0 /= a2;
  } while (a0 != 0);
  # Writing '\0' to the end of string and moving to last string char
  writechar(a1, x0--, '\0');
  # Reversing string
  for (x1 = 0; x1 < x0; x0--, x1++) {
    # Fetching temporary char
    x2 = readchar(a1, x1);
    # Writing from end to start
    writechar(a1, x1, readchar(a1, x0));
    # Writing temporary char to end
    writechar(a1, x0, x2);
  }
  # Returning result buffer
  return a1;
}

### TLSF structure
# 16 bytes                                              - null block
# 4 bytes                                               - first level bitmap
# 4 * MMM_TLSF_FL_INDEX_COUNT                           - second level bitmap
# 4 * MMM_TLSF_FL_INDEX_COUNT * MMM_TLSF_SL_INDEX_COUNT - matrix with free blocks
MMM_TLSF_STRUCT_BLOCK  => 0;
MMM_TLSF_STRUCT_FL_MAP => 4;
MMM_TLSF_STRUCT_SL_MAP => 5;
MMM_TLSF_STRUCT_MATRIX => 5 + MMM_TLSF_FL_INDEX_COUNT;
MMM_TLSF_STRUCT_SIZE   => 4 * (MMM_TLSF_STRUCT_MATRIX + MMM_TLSF_FL_INDEX_COUNT * MMM_TLSF_SL_INDEX_COUNT);

### TLSF block header structure
MMM_TLSF_BLOCK_STRUCT_SIZE     => 16;
MMM_TLSF_BLOCK_STRUCT_OFFSET   => 8;
MMM_TLSF_BLOCK_STRUCT_OVERHEAD => 4;
# 4 bytes - pointer to previous block header
# 4 bytes - size of block + bits for availability of previous/this block
# 4 bytes - pointer to previous free block header (here starts data if used)
# 4 bytes - pointer to next free block header
MMM_TLSF_BLOCK_PREV          => 0;
MMM_TLSF_BLOCK_SIZE          => 1;
MMM_TLSF_BLOCK_SIZE_MASK     => 0xfffffffc;
MMM_TLSF_BLOCK_SIZE_MIN      => MMM_TLSF_BLOCK_STRUCT_SIZE - MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
MMM_TLSF_BLOCK_SIZE_MAX      => 1 << MMM_TLSF_STRUCT_FL_MAP;
MMM_TLSF_BLOCK_FREE_BIT      => 1 << 0;
MMM_TLSF_BLOCK_PREV_FREE_BIT => 1 << 1;
MMM_TLSF_BLOCK_BIT_MASK      => 0x00000002;
MMM_TLSF_BLOCK_DATA          => 2;
MMM_TLSF_BLOCK_PREV_FREE     => 2;
MMM_TLSF_BLOCK_NEXT_FREE     => 3;

### TLSF pool header structure
MMM_TLSF_POOL_OVERHEAD => 2 * MMM_TLSF_BLOCK_STRUCT_OVERHEAD;

## get_new_area - get new memory area
# a0 - size
# @return area address
DEFAULT_AREA_SIZE => 0x1000;
get_new_area : (a0) {
  # x0 - area
  allocate(1);
  # Fetching area address
  x0 = sbrk(NULL);
  # If allocated successfully, returning area address
  if (sbrk(a0) != 0xffffffff) return x0;
  return NULL;
}

## get_memory_pool - get or initialize default memory pool
# @return default memory pool
get_memory_pool : () {
  # x0 - memory pool
  # x1 - area size
  # x2 - area
  allocate(3);
  # Fetching default memory pool
  x0 = default_memory_pool;
  # Initializing, if it is NULL
  if (!x0) {
    x1 = max(MMM_TLSF_STRUCT_SIZE, DEFAULT_AREA_SIZE);
    # Fetching new area
    x2 = get_new_area(x1);
    # Panic if new area is NULL
    if (!x2) panic("Couldn't allocate new area");
    # Initializing memory pool
    mmm_tlsf_create_with_pool(x2, x1);
    # Setting variables
    default_memory_pool = x2;
    x0 = x2;
  }
  # Returning memory pool
  return x0;
}

### TLSF block functions
## mmm_tlsf_block_size - get block size
# a0 - block
# @return block size
mmm_tlsf_block_size : (a0) {
  return a0[MMM_TLSF_BLOCK_SIZE] & MMM_TLSF_BLOCK_SIZE_MASK;
}

## mmm_tlsf_can_block_split - can block split by size?
# a0 - block
# a1 - size
# @return can block split
mmm_tlsf_can_block_split : (a0, a1) {
  return mmm_tlsf_block_size(a0) >= MMM_TLSF_BLOCK_STRUCT_SIZE + a1;
}

## mmm_tlsf_set_block_size - set block size
# a0 - block
# a1 - size
mmm_tlsf_set_block_size : (a0, a1) {
  # x0 - bits
  allocate(1);
  # Fetching bits
  x0 = a0[MMM_TLSF_BLOCK_SIZE] & MMM_TLSF_BLOCK_BIT_MASK;
  # Setting bits to new size
  a0[MMM_TLSF_BLOCK_SIZE] = a1 | x0;
}

## mmm_tlsf_is_block_free - is block free?
# a0 - block
# @return is block free
mmm_tlsf_is_block_free : (a0) {
  return a0[MMM_TLSF_BLOCK_SIZE] & MMM_TLSF_BLOCK_FREE_BIT;
}

## mmm_tlsf_set_block_free - set block free bit
# a0 - block
mmm_tlsf_set_block_free : (a0) {
  a0[MMM_TLSF_BLOCK_SIZE] |= MMM_TLSF_BLOCK_FREE_BIT;
}

## mmm_tlsf_set_block_used - unset block free bit
# a0 - block
mmm_tlsf_set_block_used : (a0) {
  a0[MMM_TLSF_BLOCK_SIZE] &= ~MMM_TLSF_BLOCK_FREE_BIT;
}

## mmm_tlsf_is_prev_block_free - is previous block free?
# a0 - block
# @return is previous block free
mmm_tlsf_is_prev_block_free : (a0) {
  return a0[MMM_TLSF_BLOCK_SIZE] & MMM_TLSF_BLOCK_PREV_FREE_BIT;
}

## mmm_tlsf_set_prev_block_free - set previous block free bit
# a0 - block
mmm_tlsf_set_prev_block_free : (a0) {
  a0[MMM_TLSF_BLOCK_SIZE] |= MMM_TLSF_BLOCK_PREV_FREE_BIT;
}

## mmm_tlsf_set_prev_block_used - unset previous block free bit
# a0 - block
mmm_tlsf_set_prev_block_used : (a0) {
  a0[MMM_TLSF_BLOCK_SIZE] &= ~MMM_TLSF_BLOCK_PREV_FREE_BIT;
}

## mmm_tlsf_is_block_last - is block last?
# a0 - block
# @return is block last
mmm_tlsf_is_block_last : (a0) {
  return mmm_tlsf_block_size(a0) == 0;
}

## mmm_tlsf_ptr_to_block - get block from data pointer
# a0 - pointer
# @return block
mmm_tlsf_ptr_to_block : (a0) {
  return a0 - MMM_TLSF_BLOCK_STRUCT_OFFSET;
}

## mmm_tlsf_block_to_ptr - get data pointer from block
# a0 - block
# @return data pointer
mmm_tlsf_block_to_ptr : (a0) {
  return a0 + MMM_TLSF_BLOCK_STRUCT_OFFSET;
}

## mmm_tlsf_prev_block - get previous block
# a0 - block
# @return previous block
mmm_tlsf_prev_block : (a0) {
  assert(mmm_tlsf_is_prev_block_free(a0), "Previous TLSF block must be free");
  return a0[MMM_TLSF_BLOCK_PREV];
}

## mmm_tlsf_next_block - get next block
# a0 - block
# @return next block
mmm_tlsf_next_block : (a0) {
  assert(!mmm_tlsf_is_block_last(a0), "TLSF block must be non-last");
  return a0 + mmm_tlsf_block_size(a0) + MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
}

## mmm_tlsf_link_next_block - get and link next block
# a0 - block
# @return next block
mmm_tlsf_link_next_block : (a0) {
  # x0 - next block
  allocate(1);
  # Fetching next block
  x0 = mmm_tlsf_next_block(a0);
  # Linking blocks
  x0[MMM_TLSF_BLOCK_PREV] = a0;
  # Returning next block
  return x0;
}

## mmm_tlsf_mark_block_free - mark block as free
# a0 - block
mmm_tlsf_mark_block_free : (a0) {
  # x0 - next block
  allocate(1);
  # Fetching next block
  x0 = mmm_tlsf_next_block(a0);
  # Setting flags
  mmm_tlsf_set_prev_block_free(x0);
  mmm_tlsf_set_block_free(a0);
}

## mmm_tlsf_mark_block_used - mark block as used
# a0 - block
mmm_tlsf_mark_block_used : (a0) {
  # x0 - next block
  allocate(1);
  # Fetching next block
  x0 = mmm_tlsf_next_block(a0);
  # Setting flags
  mmm_tlsf_set_prev_block_used(x0);
  mmm_tlsf_set_block_used(a0);
}

### TLSF utility functions
## mmm_tlsf_mapping_insert - compute first and second level indices
# a0 - size
# a1 - first level index pointer
# a2 - second level index pointer
mmm_tlsf_mapping_insert : (a0, a1, a2) {
  # x0 - first level index
  # x1 - second level index
  allocate(2);
  # Calculating first level and second level indices
  if (a0 < MMM_TLSF_SMALL_BLOCK) {
    x0 = 0;
    x1 = a0 / (MMM_TLSF_SMALL_BLOCK / MMM_TLSF_SL_INDEX_COUNT);
  } else {
    x0 = ffs(a0);
    x1 = (a0 >> (x0 - MMM_TLSF_SL_INDEX_COUNT_LOG2)) ^ MMM_TLSF_SL_INDEX_COUNT;
    x0 -= MMM_TLSF_FL_INDEX_SHIFT - 1;
  }
  # Storing results to pointers
  *a1 = x0;
  *a2 = x1;
}

## mmm_tlsf_mapping_search - compute first and second level indices with round up
# a0 - size
# a1 - first level index pointer
# a2 - second level index pointer
mmm_tlsf_mapping_search : (a0, a1, a2) {
  if (a0 >= MMM_TLSF_SMALL_BLOCK)
    a0 += (1 << fls(a0) - MMM_TLSF_SL_INDEX_COUNT_LOG2) - 1;
  mmm_tlsf_mapping_insert(a0, a1, a2);
}

## mmm_tlsf_find_suitable_block - find a suitable free block
# a0 - TLSF structure
# a1 - first level index pointer
# a2 - second level index pointer
# @return block header
mmm_tlsf_find_suitable_block : (a0, a1, a2) {
  # x0 - first level index
  # x1 - second level index
  # x2 - first level bitmap
  # x3 - second level bitmap
  allocate(4);
  # Fetching first level and second level indices
  x0 = *a1;
  x1 = *a2;
  # Fetching second level bitmap
  x3 = a0[MMM_TLSF_STRUCT_SL_MAP + x0] & (0xffffffff << x1);
  # If second level bitmap is NULL
  if (!x3) {
    # Then no block exists. Search in the next largest first-level list
    x2 = a0[MMM_TLSF_STRUCT_FL_MAP] & (0xffffffff << (x0 + 1));
    # If first level bitmap is NULL, no free blocks available
    if (!x2) return NULL;
    # We have free blocks, calculating first level index for them
    x0 = ffs(x2);
    *a1 = x0;
    # Calculating second level bitmap
    x3 = a0[MMM_TLSF_STRUCT_SL_MAP + x0];
  }
  # Calculating second level index
  assert(x3, "Second level bitmap is NULL");
  x1 = ffs(x3);
  *a2 = x1;
  # Returning first block in free list
  return a0[MMM_TLSF_STRUCT_MATRIX + x0 * MMM_TLSF_SL_INDEX_COUNT + x1];
}

## mmm_tlsf_remove_free_block - remove free block from matrix
# a0 - TLSF structure
# a1 - block
# a2 - first level index
# a3 - second level index
mmm_tlsf_remove_free_block : (a0, a1, a2, a3) {
  # x0 - previous free block
  # x1 - next free block
  allocate(2);
  # Fetching free blocks
  x0 = a1[MMM_TLSF_BLOCK_PREV_FREE];
  x1 = a1[MMM_TLSF_BLOCK_NEXT_FREE];
  assert(x0, "Previous free block cannot be NULL");
  assert(x1, "Next free block cannot be NULL");
  # Updating free blocks
  x0[MMM_TLSF_BLOCK_NEXT_FREE] = x1;
  x1[MMM_TLSF_BLOCK_PREV_FREE] = x0;
  # If block is the head of the free list, set new head
  if (a0[MMM_TLSF_STRUCT_MATRIX + a2 * MMM_TLSF_SL_INDEX_COUNT + a3] == a1) {
    # New head is next free block
    a0[MMM_TLSF_STRUCT_MATRIX + a2 * MMM_TLSF_SL_INDEX_COUNT + a3] = x1;
    # If the new head is NULL, clear second level bitmap
    if (x1 == a0) {
      # Clearing second level bitmap
      a0[MMM_TLSF_STRUCT_SL_MAP + a2] &= ~(1 << a3);
      # If second level bitmap is NULL, clear first level bitmap
      if (!a0[MMM_TLSF_STRUCT_SL_MAP + a2])
        a0[MMM_TLSF_STRUCT_FL_MAP] &= ~(1 << a2);
    }
  }
}

## mmm_tlsf_insert_free_block - insert free block to matrix
# a0 - TLSF structure
# a1 - block
# a2 - first level index
# a3 - second level index
mmm_tlsf_insert_free_block : (a0, a1, a2, a3) {
  # x0 - current free block
  allocate(1);
  # Fetching current free list head
  x0 = a0[MMM_TLSF_STRUCT_MATRIX + a2 * MMM_TLSF_SL_INDEX_COUNT + a3];
  assert(x0, "Free list cannot have a NULL entry");
  assert(a1, "Cannot insert a null entry into the free list");
  # Updating free block
  a1[MMM_TLSF_BLOCK_PREV_FREE] = a0;
  a1[MMM_TLSF_BLOCK_NEXT_FREE] = x0;
  x0[MMM_TLSF_BLOCK_PREV_FREE] = a1;
  # Making new block a head of free blocks list
  a0[MMM_TLSF_STRUCT_MATRIX + a2 * MMM_TLSF_SL_INDEX_COUNT + a3] = a1;
  # Setting first level bitmap
  a0[MMM_TLSF_STRUCT_FL_MAP] |= 1 << a2;
  # Setting second level bitmap
  a0[MMM_TLSF_STRUCT_SL_MAP + a2] |= 1 << a3;
}

## mmm_tlsf_remove_block - remove block from matrix
# a0 - TLSF structure
# a1 - block
mmm_tlsf_remove_block : (a0, a1) {
  # x0 - first level index
  # x1 - second level index
  allocate(2);
  # Fetching first level and second level indices
  mmm_tlsf_mapping_insert(mmm_tlsf_block_size(a1), &x0, &x1);
  mmm_tlsf_remove_free_block(a0, a1, x0, x1);
}

## mmm_tlsf_insert_block - insert block to matrix
# a0 - TLSF structure
# a1 - block
mmm_tlsf_insert_block : (a0, a1) {
  # x0 - first level index
  # x1 - second level index
  allocate(2);
  # Fetching first level and second level indices
  mmm_tlsf_mapping_insert(mmm_tlsf_block_size(a1), &x0, &x1);
  mmm_tlsf_insert_free_block(a0, a1, x0, x1);
}

## mmm_tlsf_split_block - split block by size
# a0 - block
# a1 - size
# @return remaining block
mmm_tlsf_split_block : (a0, a1) {
  # x0 - remaining block
  # x1 - remaining size
  allocate(2);
  # Fetching remaining block pointer
  x0 = a0 + MMM_TLSF_BLOCK_STRUCT_OFFSET + a1 - MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
  # Fetching remaining block size
  x1 = mmm_tlsf_block_size(a0) - a1 - MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
  # TODO: make assert for alignment of address
  # Setting size for blocks
  mmm_tlsf_set_block_size(a0, a1);
  mmm_tlsf_set_block_size(x0, x1);
  assert(mmm_tlsf_block_size(x0) >= MMM_TLSF_BLOCK_SIZE_MIN, "Block split with incorrect size");
  # Marking remaining block as free
  mmm_tlsf_set_block_free(x0);
  # Returning remaining block
  return x0;
}

## mmm_tlsf_join_blocks - join blocks together
# a0 - prev block
# a1 - block
# @return joined previous block
mmm_tlsf_join_blocks : (a0, a1) {
  assert(!mmm_tlsf_is_block_last(a0), "Previous TLSF block can't be last");
  a0[MMM_TLSF_BLOCK_SIZE] += mmm_tlsf_block_size(a1) + MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
  # Linking next block
  mmm_tlsf_link_next_block(a0);
  # Returning joined block
  return a0;
}

## mmm_tlsf_merge_prev_block - merge block with previous
# a0 - TLSF structure
# a1 - block
mmm_tlsf_merge_prev_block : (a0, a1) {
  # x0 - previous block
  allocate(1);
  # Merging only if previous block is free
  if (mmm_tlsf_is_prev_block_free(a1)) {
    # Fetching previous block
    x0 = mmm_tlsf_prev_block(a1);
    assert(x0, "Previous TLSF block can't be NULL");
    assert(mmm_tlsf_is_block_free(x0), "Previous TLSF block is not free though marked as such");
    # Removing previous block from matrix
    mmm_tlsf_remove_block(a0, x0);
    # Joining blocks
    a1 = mmm_tlsf_join_blocks(x0, a1);
  }
  # Returning block
  return a1;
}

## mmm_tlsf_merge_next_block - merge block with next
# a0 - TLSF structure
# a1 - block
mmm_tlsf_merge_next_block : (a0, a1) {
  # x0 - next block
  allocate(1);
  # Fetching new block
  x0 = mmm_tlsf_next_block(a1);
  assert(x0, "Next TLSF block can't be NULL");
  # Merging only if next block is free
  if (mmm_tlsf_is_block_free(x0)) {
    assert(!mmm_tlsf_is_block_last(a1), "Previous block can't be last");
    # Removing next block from matrix
    mmm_tlsf_remove_block(a0, x0);
    # Joining blocks
    a1 = mmm_tlsf_join_blocks(a1, x0);
  }
  # Returning block
  return a1;
}

## mmm_tlsf_trim_free_block - trim space from end of free block
# a0 - TLSF structure
# a1 - block
# a2 - size
mmm_tlsf_trim_free_block : (a0, a1, a2) {
  # x0 - remaining block
  allocate(1);
  assert(mmm_tlsf_is_block_free(a1), "TLSF block must be free");
  # We can trim only if we can split block
  if (mmm_tlsf_can_block_split(a1, a2)) {
    # Splitting block
    x0 = mmm_tlsf_split_block(a1, a2);
    # Linking block
    mmm_tlsf_link_next_block(a1);
    # Setting previous block free
    mmm_tlsf_set_prev_block_free(x0);
    # Inserting block in matrix
    mmm_tlsf_insert_block(a0, x0);
  }
}

## mmm_tlsf_block_trim_used - trim space from end of used block
# a0 - TLSF structure
# a1 - block
# a2 - size
mmm_tlsf_trim_used_block : (a0, a1, a2) {
  # x0 - remaining block
  allocate(1);
  assert(!mmm_tlsf_is_block_free(a1), "TLSF block must be used");
  # We can trim only if we can split block
  if (mmm_tlsf_can_block_split(a1, a2)) {
    # Splitting block
    x0 = mmm_tlsf_split_block(a1, a2);
    # Setting previous block used
    mmm_tlsf_set_prev_block_used(x0);
    # Merging remaining block with next
    x0 = mmm_tlsf_merge_next_block(a0, x0);
    # Inserting block in matrix
    mmm_tlsf_insert_block(a0, x0);
  }
}

## mmm_tlsf_trim_free_block_leading - trim space from end of free block
# a0 - TLSF structure
# a1 - block
# a2 - size
# @return remaining block
mmm_tlsf_trim_free_block_leading : (a0, a1, a2) {
  # We can trim only if we can split block
  if (mmm_tlsf_can_block_split(a1, a2)) {
    # Splitting block
    a1 = mmm_tlsf_split_block(a1, a2);
    # Setting previous block free
    mmm_tlsf_set_prev_block_free(a1);
    # Linking block
    mmm_tlsf_link_next_block(a1);
    # Inserting block in matrix
    mmm_tlsf_insert_block(a0, a1);
  }
  # Returning block
  return a1;
}

## mmm_tlsf_locate_free_block - locate free block by size
# a0 - TLSF structure
# a1 - size
# @return block
mmm_tlsf_locate_free_block : (a0, a1) {
  # x0 - first level index
  # x1 - second level index
  # x2 - block
  allocate(3);
  # Resetting variables
  x0 = NULL; x1 = NULL; x2 = NULL;
  # Searching indices if size is non-NULL
  if (a1) {
    # Computing first and second level indices
    mmm_tlsf_mapping_search(a1, &x0, &x1);
    # If first level is fitting, fetching block
    if (x0 < MMM_TLSF_FL_INDEX_COUNT)
      x2 = mmm_tlsf_find_suitable_block(a0, &x0, &x1);
  }
  # If block is found, removing it from matrix
  if (x2) mmm_tlsf_remove_free_block(a0, x2, x0, x1);
  # Returning block
  return x2;
}

## mmm_tlsf_prepare_block - prepare block with size
# a0 - TLSF structure
# a1 - free block
# a2 - size
# @return block
mmm_tlsf_prepare_block : (a0, a1, a2) {
  # x0 - block
  allocate(1);
  # Trimming block, if free block is non-NULL
  if (a1) {
    assert(a2, "Size must be non-NULL");
    # Trimming block
    mmm_tlsf_trim_free_block(a0, a1, a2);
    # Marking block as used
    mmm_tlsf_mark_block_used(a1);
    # Returning block
    return mmm_tlsf_block_to_ptr(a1);
  }
  # Returning NULL
  return NULL;
}

## mmm_tlsf_construct - initialize TLSF structure
# a0 - TLSF structure
mmm_tlsf_construct : (a0) {
  # x0 - i
  # x1 - j
  # x2 - null block
  allocate(3);
  # Fetching null block
  x2 = a0;
  # Initializing null block
  x2[MMM_TLSF_BLOCK_PREV_FREE] = x2;
  x2[MMM_TLSF_BLOCK_NEXT_FREE] = x2;
  # Zeroing first level index
  a0[MMM_TLSF_STRUCT_FL_MAP] = 0;
  for (x0 = 0; x0 < MMM_TLSF_FL_INDEX_COUNT; x0++) {
    # Zeroing second level index
    a0[MMM_TLSF_STRUCT_SL_MAP + x0] = 0;
    for (x1 = 0; x1 < MMM_TLSF_SL_INDEX_COUNT; x1++)
      a0[MMM_TLSF_STRUCT_MATRIX + x0 * MMM_TLSF_SL_INDEX_COUNT + x1] = x2;
  }
}

## mmm_tlsf_add_pool - add pool to TLSF structure
# a0 - TLSF structure
# a1 - pointer
# a2 - pool size
# @return pool
mmm_tlsf_add_pool : (a0, a1, a2) {
  # x0 - pool bytes
  # x1 - main free block
  # x2 - next block
  allocate(3);
  # Calculate pool bytes
  x0 = align_down(a2 - MMM_TLSF_POOL_OVERHEAD, MMM_TLSF_ALIGN_SIZE);
  # assert(a1 % MMM_TLSF_ALIGN_SIZE == 0, "Memory must be aligned by ALIGN_SIZE");
  # assert(x0 >= MMM_TLSF_BLOCK_SIZE_MIN && x0 <= MMM_TLSF_BLOCK_SIZE_MAX);
  # First free block is at the start of pool minus overhead
  x1 = a1 - MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
  mmm_tlsf_set_block_size(x1, x0);
  mmm_tlsf_set_block_free(x1);
  mmm_tlsf_set_prev_block_used(x1);
  mmm_tlsf_insert_block(a0, x1);
  # Split block to create a zero-size block
  x2 = mmm_tlsf_link_next_block(x1);
  mmm_tlsf_set_block_size(x2, 0);
  mmm_tlsf_set_block_used(x2);
  mmm_tlsf_set_prev_block_free(x2);
  # Returning pointer
  return a1;
}

## mmm_tlsf_remove_pool - remove pool from TLSF structure
# a0 - TLSF structure
# a1 - pool
mmm_tlsf_remove_pool : (a0, a1, a2) {
  # x0 - block
  # x1 - first level index
  # x2 - second level index
  allocate(3);
  # Initializing indices
  x1 = 0; x2 = 0;
  # Fetching block
  x0 = a1 - MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
  assert(mmm_tlsf_is_block_free(x0), "TLSF block must be free");
  assert(!mmm_tlsf_is_block_free(mmm_tlsf_next_block(x0)), "Next TLSF block must be used");
  assert(mmm_tlsf_block_size(mmm_tlsf_next_block(x0)), "Next TLSF block must have size = 0");
  # Fetching indices for this block
  mmm_tlsf_mapping_insert(mmm_tlsf_block_size(x0), &x1, &x2);
  # Removing block from matrix
  mmm_tlsf_remove_free_block(a0, x0, x1, x2);
}

## mmm_tlsf_create - create TLSF structure at pointer
# a0 - pointer
# @return TLSF structure
mmm_tlsf_create : (a0) {
  # assert(a0 % MMM_TLSF_ALIGN_SIZE == 0, "Memory must be aligned by ALIGN_SIZE");
  # Initializing TLSF
  mmm_tlsf_construct(a0);
  # Returning TLSF
  return a0;
}

## mmm_tlsf_create_with_pool - create TLSF structure with pool at pointer
# a0 - pointer
# a1 - bytes count
# @return TLSF structure
mmm_tlsf_create_with_pool : (a0, a1) {
  # Initializing TLSF
  a0 = mmm_tlsf_create(a0);
  # Adding pool to TLSF structure
  mmm_tlsf_add_pool(a0, a0 + MMM_TLSF_STRUCT_SIZE, a1 - MMM_TLSF_STRUCT_SIZE);
  # Returning TLSF
  return a0;
}

## mmm_malloc - allocate block with size from TLSF
# a0 - TLSF structure
# a1 - size
# @return memory address
mmm_malloc : (a0, a1) {
  # x0 - adjusted size
  # x1 - free block
  # x2 - area size
  # x3 - area
  allocate(4);
  # Adjusting size
  x0 = align_up(a1, MMM_TLSF_ALIGN_SIZE);
  # Fetching free block
  x1 = mmm_tlsf_locate_free_block(a0, x0);
  # If not found, and TLSF is default memory pool, expanding it and searching again
  if (!x1 && a0 && a0 == default_memory_pool) {
    # Calculating area size
    x2 = x0 + MMM_TLSF_POOL_OVERHEAD;
    x2 = x2 > DEFAULT_AREA_SIZE ? align_up(x2, DEFAULT_AREA_SIZE) : DEFAULT_AREA_SIZE;
    # Getting new area
    x3 = get_new_area(x2);
    # Panic if new area is NULL
    if (!x3) panic("Couldn't allocate new area");
    # Adding pool to TLSF
    mmm_tlsf_add_pool(a0, x3, x2);
    # Fetching free block again
    x1 = mmm_tlsf_locate_free_block(a0, x0);
  }
  # Returning new block
  return mmm_tlsf_prepare_block(a0, x1, x0);
}

## mmm_memalign - allocate block with aligned size from TLSF
# a0 - TLSF structure
# a1 - alignment
# a2 - size
# @return memory address
mmm_memalign : (a0, a1, a2) {
  # x0 - adjusted size
  # x1 - size with gap
  # x2 - aligned size
  # x3 - free block
  # x4 - pointer
  # x5 - gap size
  # x6 - next aligned boundary
  allocate(7);
  # Adjusting size
  x0 = align_up(a2, MMM_TLSF_ALIGN_SIZE);
  # Calculating size with gap
  x1 = align_up(x0 + a1 + MMM_TLSF_BLOCK_STRUCT_SIZE, a1);
  # Calculating aligned size
  x2 = x0 && a1 > MMM_TLSF_ALIGN_SIZE ? x1 : x0;
  # Fetching free block
  x3 = mmm_tlsf_locate_free_block(a0, x2);
  # If block is fetched, aligning
  if (x3) {
    # Fetching pointer from block
    x4 = mmm_tlsf_block_to_ptr(x3);
    # Calculating gap
    x5 = align_up(x4, a1) - x4;
    # If gap is too small, offset to next aligned boundary
    if (x5 && x5 < MMM_TLSF_BLOCK_STRUCT_SIZE) {
      # Calculating next aligned boundary
      x6 = max(MMM_TLSF_BLOCK_STRUCT_SIZE - x5, a1);
      # Calculating new gap
      x5 = align_up(x6, a1) - x4;
    }
    # If gap is non-NULL, trimming block
    if (x5) {
      assert(x5 >= MMM_TLSF_BLOCK_STRUCT_SIZE, "Gap size is too small");
      x3 = mmm_tlsf_trim_free_block_leading(a0, x3, x5);
    }
  }
  # Returning new block
  return mmm_tlsf_prepare_block(a0, x2, x0);
}

## mmm_free - free block from TLSF
# a0 - TLSF structure
# a1 - memory address
mmm_free : (a0, a1) {
  # x0 - block
  allocate(1);
  # We can free only non-NULL address
  if (a1) {
    # Get block from pointer
    x0 = mmm_tlsf_ptr_to_block(a1);
    # Mark block as free
    mmm_tlsf_mark_block_free(x0);
    # Merge block with siblings
    x0 = mmm_tlsf_merge_prev_block(a0, x0);
    x0 = mmm_tlsf_merge_next_block(a0, x0);
    # Insert block in matrix
    mmm_tlsf_insert_block(a0, x0);
  }
}

## mmm_realloc - reallocate block with size from TLSF
# a0 - TLSF structure
# a1 - memory address
# a2 - new size
#
# a1 == NULL && a2 != NULL -> realloc == malloc
# a1 != NULL && a2 == NULL -> realloc == free
#
# @return memory address
mmm_realloc : (a0, a1, a2) {
  # x0 - pointer
  # x1 - block
  # x2 - next block
  # x3 - current block size
  # x4 - combined size
  # x5 - adjusted size
  allocate(6);
  # Initializing pointer
  x0 = NULL;
  # Checking use cases
  if (a1 && !a2) mmm_free(a0, a1);
  else if (!a1)  mmm_malloc(a0, a2);
  else {
    # Fetching block from pointer
    x1 = mmm_tlsf_ptr_to_block(a1);
    # Fetching next block
    x2 = mmm_tlsf_next_block(x1);
    # Fetching block size
    x3 = mmm_tlsf_block_size(x1);
    # Calculating combined size
    x4 = x3 + mmm_tlsf_block_size(x2) + MMM_TLSF_BLOCK_STRUCT_OVERHEAD;
    # Calculating adjusted size
    x5 = align_up(x4, MMM_TLSF_ALIGN_SIZE);
    assert(!mmm_tlsf_is_block_free(x1), "Block is already marked as free");
    # If next block is used or when combined doesn't offer enough space, reallocate
    if (x5 > x3 && (!mmm_tlsf_is_block_free(x2) || x5 > x4)) {
      # Allocating new block
      x0 = mmm_malloc(a0, a2);
      if (x0) {
        # Copying data to new block
        memcpy(a1, x0, min(x3, a2));
        # Freeing old block
        mmm_free(a0, a1);
      }
    } else {
      # Do we need to expand to next block?
      if (x5 > x3) {
        # Merging with next block
        mmm_tlsf_merge_next_block(a0, x1);
        # Marking as used
        mmm_tlsf_set_block_used(x1);
      }
      # Trimming block
      mmm_tlsf_trim_used_block(a0, x1, x5);
      # Setting pointer to initial
      x0 = a1;
    }
  }
  # Returning pointer
  return x0;
}

## mmm_calloc - allocate block with N elements size from TLSF
# a0 - TLSF structure
# a1 - element size
# a2 - element count
# @return memory address
mmm_calloc : (a0, a1, a2) {
  # x0 - pointer
  allocate(1);
  # Initializing pointer
  x0 = NULL;
  # Allocating, when total size is non-NULL
  if (a1 && a2) {
    # Allocating block
    x0 = mmm_malloc(a0, a1 * a2);
    # Zeroing block content, if allocated block
    if (x0) memset(0, x0, a1 * a2);
  }
  # Returning pointer
  return x0;
}

## malloc - allocate block with size
# a0 - size
# @return memory address
malloc : (a0) { return mmm_malloc(get_memory_pool(), a0); }

## free - free block
# a0 - memory address
free : (a0) { return mmm_free(get_memory_pool(), a0); }

## realloc - reallocate block with new size
# a0 - memory address
# a1 - new size
# @return memory address
realloc : (a0, a1) { return mmm_realloc(get_memory_pool(), a0, a1); }

## calloc - allocate block with N elements size
# a0 - element size
# a1 - element count
# @return memory address
calloc : (a0, a1) { return mmm_calloc(get_memory_pool(), a0, a1); }

#### Data Structures
### Tuples
## tuple - create tuple
# a0 - number of elements
# ... - elements
tuple : (a0, ...) {
  # x0 - va pointer
  # x1 - tuple
  # x2 - i
  allocate(3);
  # Initialize variadic argument pointer
  x0 = &a0 + 4;
  # Allocate tuple
  x1 = calloc(4, a0);
  # Fetching tuple values
  for (x2 = 0; x2 < a0; x2++) x1[x2] = x0[x2];
  # Return tuple
  return x1;
}

### Linked List
## List parts
LIST_NEXT  => 0;
LIST_VALUE => 1;

## list - create linked list with value
# a0 - value
# @return list
list : (a0) { return list_insert(NULL, a0); }

## list_insert - insert value in linked list
# a0 - list
# a1 - value
# @return list with new value
list_insert : (a0, a1) { return tuple(2, a0, a1); }

## list_next - get next element of linked list
# a0 - list
# @return list with next value
list_next : (a0) { return a0[LIST_NEXT]; }

## list_value - get value of current element of linked list
# a0 - list
# @return current element value
list_value : (a0) { return a0[LIST_VALUE]; }

## list_pop - remove this element from list
# a0 - list
# @return reduced list
list_pop : (a0) {
  # x0 - list continuation
  allocate(1);
  # Fetching next element
  x0 = list_next(a0);
  # Deallocating current element
  free(a0);
  return x0;
}

### Vector
## Vector parts
VEC_SIZE => 0;
VEC_CAP  => 1;
VEC_BUF  => 2;

## char_vector - create character vector
# a0 - size
# a1 - capacity
# @return character vector
char_vector : (a0, a1) { return tuple(3, a0, a1, calloc(1, a1)); }

## vector - create vector
# a0 - size
# a1 - capacity
# @return vector
vector : (a0, a1) { return tuple(3, a0, a1, calloc(4, a1)); }

## vector_buffer - get vector buffer
# a0 - vector
# @return vector buffer
vector_buffer : (a0) { return a0[VEC_BUF]; }

## vector_size - get vector size
# a0 - vector
# @return vector size
vector_size : (a0) { return a0[VEC_SIZE]; }

## char_vector_reserve - reserve more space for char vector buffer
# a0 - char vector
# a1 - new capacity
char_vector_reserve : (a0, a1) {
  # Reserving, if new capacity is bigger
  if (a1 > a0[VEC_CAP]) {
    # Reallocating buffer
    a0[VEC_BUF] = realloc(a0[VEC_BUF], a1);
    # Setting new capacity
    a0[VEC_CAP] = a1;
  }
}

## vector_reserve - reserve more space for vector buffer
# a0 - vector
# a1 - new capacity
vector_reserve : (a0, a1) {
  # Reserving, if new capacity is bigger
  if (a1 > a0[VEC_CAP]) {
    # Reallocating buffer
    a0[VEC_BUF] = realloc(a0[VEC_BUF], 4 * a1);
    # Setting new capacity
    a0[VEC_CAP] = a1;
  }
}

## char_vector_resize - resize char vector
# a0 - char vector
# a1 - new size
char_vector_resize : (a0, a1) {
  # Reserving, if required
  if (a1 > a0[VEC_SIZE]) char_vector_reserve(a0, a1);
  # Setting new size
  a0[VEC_SIZE] = a1;
}

## vector_resize - resize vector
# a0 - vector
# a1 - new size
vector_resize : (a0, a1) {
  # Reserving, if required
  if (a1 > a0[VEC_SIZE]) vector_reserve(a0, a1);
  # Setting new size
  a0[VEC_SIZE] = a1;
}

## char_vector_get - get value at index in char vector
# a0 - char vector
# a1 - index
# @return char
char_vector_get : (a0, a1) { return readchar(a0[VEC_BUF], a1); }

## vector_get - get value at index in vector
# a0 - vector
# a1 - index
# @return value
vector_get : (a0, a1) { return a0[VEC_BUF][a1]; }

## char_vector_get - set value at index in char vector
# a0 - char vector
# a1 - index
# a2 - char
char_vector_set : (a0, a1, a2) { writechar(a0[VEC_BUF], a1, a2); }

## vector_set - set value at index in vector
# a0 - vector
# a1 - index
# a2 - value
vector_set : (a0, a1, a2) {
  # x0 - buffer
  allocate(1);
  x0 = a0[VEC_BUF];
  x0[a1] = a2;
}

## char_vector_push - insert value in the end of char vector
# a0 - char vector
# a1 - value
char_vector_push : (a0, a1) {
  # If vector buffer is full, reserving more
  if (a0[VEC_SIZE] == a0[VEC_CAP])
    char_vector_reserve(a0, a0[VEC_CAP] * 2);
  # Putting new value and incrementing size
  char_vector_set(a0, a0[VEC_SIZE]++, a1);
}

## vector_push - insert value in the end of vector
# a0 - vector
# a1 - value
vector_push : (a0, a1) {
  # If vector buffer is full, reserving more
  if (a0[VEC_SIZE] == a0[VEC_CAP])
    vector_reserve(a0, a0[VEC_CAP] * 2);
  # Putting new value and incrementing size
  vector_set(a0, a0[VEC_SIZE]++, a1);
}

## char_vector_pop - remove and return last value from char vector
# a0 - char vector
# @return char
char_vector_pop : (a0) {
  # Decrementing size
  a0[VEC_SIZE] = a0[VEC_SIZE] - 1;
  # Returning the value
  return char_vector_get(a0, a0[VEC_SIZE]);
}

## vector_pop - remove and return last value from vector
# a0 - vector
# @return value
vector_pop : (a0) {
  # Decrementing size
  a0[VEC_SIZE] = a0[VEC_SIZE] - 1;
  # Returning the value
  return vector_get(a0, a0[VEC_SIZE]);
}

## vector_destroy - destroy vector
# a0 - vector
vector_destroy : (a0) {
  # Deallocating buffer
  free(a0[VEC_BUF]);
  # Deallocating vector
  free(a0);
}

### I/O
## Open flags
O_RDONLY => 0;
O_WRONLY => 1;
O_RDWR   => 2;
O_CREAT  => 64;
O_TRUNC  => 512;
O_APPEND => 1024;
## Parts of I/O channels
CHAN_FD   => 0;
CHAN_BUF  => 1;
CHAN_IDX  => 2;
ICHAN_END => 3;
ICHAN_EOF => 4;
## Buffer sizes
IBUFFER_SIZE => 512;
OBUFFER_SIZE => 512;
## Standard channels
stdin  : NULL;
stdout : NULL;
stderr : NULL;

## input_chan - create new input channel
# a0 - fd
# @return channel
input_chan : (a0) { return tuple(5, a0, malloc(IBUFFER_SIZE), 0, 0, FALSE); }

## input_chan_file - create new input channel based on file
# a0 - file name
# @return channel
input_chan_file : (a0) {
  # x0 - fd/channel
  allocate(1);
  # Opening file with read permission
  x0 = open(a0, O_RDONLY, 0);
  # Checking for success
  assert(x0 > 0, "Couldn't open file input channel");
  # Returning new channel
  return input_chan(x0);
}

## input_chan_close - close input channel
# a0 - channel
input_chan_close : (a0) {
  # Closing fd
  close(a0[CHAN_FD]);
  # Deallocating buffer
  free(a0[CHAN_BUF]);
  # Deallocating channel
  free(a0);
}

## output_chan - create new output channel
# a0 - fd
# @return channel
output_chan : (a0) { return tuple(3, a0, malloc(OBUFFER_SIZE), 0); }

## output_chan_file - create new output channel based on file
# a0 - file name
# @return channel
output_chan_file : (a0) {
  # x0 - fd/channel
  allocate(1);
  # Opening file with read permission
  x0 = open(a0, O_WRONLY|O_CREAT|O_TRUNC, 0644);
  # Checking for success
  assert(x0 > 0, "Couldn't open file output channel");
  # Returning new channel
  return output_chan(x0);
}

## output_chan_close - close output channel
# a0 - channel
output_chan_close : (a0) {
  # Flushing buffer
  flush(a0);
  # Closing fd
  close(a0[CHAN_FD]);
  # Deallocating buffer
  free(a0[CHAN_BUF]);
  # Deallocating channel
  free(a0);
}

## initialize_io - initialize standard I/O
initialize_io : () {
  stdin  = input_chan(0);  # stdin=0
  stdout = output_chan(1); # stdout=1
  stderr = output_chan(2); # stderr=2
}

## fill - fill input channel
# a0 - channel
fill : (a0) {
  # x0 - read count
  allocate(1);
  # Checking if file is ended
  if (a0[ICHAN_EOF]) return;
  # Checking if buffer is exhausted
  if (a0[CHAN_IDX] == a0[ICHAN_END]) {
    # Input buffer is empty, refilling
    x0 = read(a0[CHAN_FD], a0[CHAN_BUF], IBUFFER_SIZE);
    # Resetting index and setting size
    a0[CHAN_IDX]  = 0;
    a0[ICHAN_END] = x0;
    # Setting EOF flag, if file is ended
    if (a0[ICHAN_END] == 0) a0[ICHAN_EOF] = TRUE;
  }
}

## fgetc - read char
# a0 - channel
# @return char
fgetc : (a0) {
  # x0 - char
  allocate(1);
  # Filling buffer, if needed
  fill(a0);
  # Checking if input is ended
  if (a0[CHAN_IDX] == a0[ICHAN_END]) return EOF;
  # Fetching char and incrementing index
  x0 = readchar(a0[CHAN_BUF], a0[CHAN_IDX]++);
  # Returning char
  return x0;
}

## getc - read char from stdin
# @return char
getc : () { return fgetc(stdin); }

## fnextc - look ahead next char
# a0 - channel
# a1 - offset
# @return char
fnextc : (a0, a1) {
  # Filling buffer, if needed
  fill(a0);
  # Checking if input is ended
  if (a0[CHAN_IDX] + a1 >= a0[ICHAN_END]) return EOF;
  # Fetching char
  return readchar(a0[CHAN_BUF], a0[CHAN_IDX] + a1);
}

## nextc - look ahead next char from stdin
# a0 - offset
# @return char
nextc : (a0) { return fnextc(stdin, a0); }

## flush - flush output channel
# a0 - channel
flush : (a0) {
  # If buffer is not empty
  if (a0[CHAN_IDX] > 0) {
    write(a0[CHAN_FD], a0[CHAN_BUF], a0[CHAN_IDX]);
    a0[CHAN_IDX] = 0;
  }
}

## fputc - put character
# a0 - channel
# a1 - char
fputc : (a0, a1) {
  # Writing to buffer and incrementing index
  writechar(a0[CHAN_BUF], a0[CHAN_IDX]++, a1);
  # Checking if need to flush
  if (a1 == '\n' || a0[CHAN_IDX] == OBUFFER_SIZE) flush(a0);
}

## putc - put character in stdout
# a0 - char
putc  : (a0) { fputc(stdout, a0); }
## eputc - put character in stderr
# a0 - char
eputc : (a0) { fputc(stderr, a0); }

## fputs - put string
# a0 - channel
# a1 - string
fputs : (a0, a1) {
  # x0 - char
  allocate(1);
  # Iterating through chars in string and printing them
  for (; x0 = readchar(a1, 0); a1++) fputc(a0, x0);
}

## puts - put string in stdout
# a0 - char
puts  : (a0) { fputs(stdout, a0); }
## eputs - put string in stderr
# a0 - char
eputs : (a0) { fputs(stderr, a0); }

## fputd - put number
# a0 - channel
# a1 - number
# a2 - base
fputn_buffer : char[33];
fputn : (a0, a1, a2) {
  # Clearing buffer
  memset(0, &fputn_buffer, 33);
  # Converting number to string
  utoa(a1, &fputn_buffer, a2);
  # Writing string to channel
  fputs(a0, &fputn_buffer);
}

## vfprintf - format and print data
# a0 - output channel
# a1 - format string
# a2 - va pointer
vfprintf : (a0, a1, a2) {
  # x0 - i
  # x1 - char
  allocate(2);
  # Iterating format string char by char
  for (x0 = 0; x1 = readchar(a1, x0); x0++) {
    # Searching for some format
    if (x1 != '%') { fputc(a0, x1); continue; }
    # Fetching format char
    x1 = readchar(a1, ++x0);
    # Picking what to do
    if      (x1 == 's') fputs(a0, *a2);
    else if (x1 == 'o') fputn(a0, *a2, 8);
    else if (x1 == 'd') fputn(a0, *a2, 10);
    else if (x1 == 'x') fputn(a0, *a2, 16);
    else if (x1 == 'c') fputc(a0, *a2);
    else if (x1 == '%') fputc(a0, '%');
    # If char wasn't %, incrementing va pointer
    if (x1 != '%') a2 += 4;
  }
}

## fprintf - format and print data to output channel
# a0 - output channel
# a1 - format string
# ... - arguments
fprintf : (a0, a1, ...) { vfprintf(a0, a1, &a1 + 4); }
## printf - format and print data to stdout
# a0 - format string
# ... - arguments
printf : (a0, ...) { vfprintf(stdout, a0, &a0 + 4); }
## eprintf - format and print data to stderr
# a0 - format string
# ... - arguments
eprintf : (a0, ...) { vfprintf(stderr, a0, &a0 + 4); }
